﻿using System;
using System.Linq;
using System.Reflection;
using System.ComponentModel;

namespace Sencha.Serialization.Contracts
{
	internal sealed class ReflectionMetadataDescriber : MetadataDescriber
	{
		public bool IncludePrivateMembers { get; set; }

        public override Attribute[] GetAttributes(MemberInfo member)
		{
			if (member == null)
				throw new ArgumentNullException("member");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

            return member.GetCustomAttributes(true).Cast<Attribute>().ToArray();
		}
        public override string GetName(MemberInfo member)
		{
			if (member == null)
				throw new ArgumentNullException("member");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

            return member.Name;
		}
        public override MemberInfo[] GetMembers(Type contractedType)
		{
			if (contractedType == null)
				throw new ArgumentNullException("contractedType");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var props = contractedType.GetProperties(BindingFlags.Instance | BindingFlags.Public | (IncludePrivateMembers ? BindingFlags.NonPublic : 0));
			var fields = contractedType.GetFields(BindingFlags.Instance | BindingFlags.Public | (IncludePrivateMembers ? BindingFlags.NonPublic : 0));

			var list = new System.Collections.ArrayList(props.Length + fields.Length);
			list.AddRange(fields);
			for (int i = 0; i < props.Length; i++)
			{
				// skip indexed properties and unaccessible getter and setter (null == null)
				if (props[i].GetIndexParameters().Length != 0 || props[i].GetGetMethod(IncludePrivateMembers) == props[i].GetSetMethod(IncludePrivateMembers))
					continue;

				list.Add(props[i]);
			}

			return (MemberInfo[])list.ToArray(typeof(MemberInfo));
		}
        public override Type GetMemberType(MemberInfo member)
		{
			if (member == null)
				throw new ArgumentNullException("member");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (member is PropertyInfo)
				return (member as PropertyInfo).PropertyType;
			else if (member is FieldInfo)
				return (member as FieldInfo).FieldType;
			else
				throw new ArgumentTypeMismatchException("member", member.GetType(), typeof(PropertyInfo), typeof(FieldInfo));
		}
        public override TypeConverter GetMemberConverter(MemberInfo member)
		{
			if (member == null)
				throw new ArgumentNullException("member");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			return TypeDescriptor.GetConverter(GetMemberType(member));
		}
        public override bool IsMemberReadOnly(MemberInfo member)
		{
			if (member == null)
				throw new ArgumentNullException("member");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (member is PropertyInfo)
				return (member as PropertyInfo).GetSetMethod(IncludePrivateMembers) == null;
			else if (member is FieldInfo)
				return (member as FieldInfo).IsInitOnly;
			else
				throw new ArgumentTypeMismatchException("member", member.GetType(), typeof(PropertyInfo), typeof(FieldInfo));
		}
        public override Type GetDeclaringType(MemberInfo member)
		{
			if (member == null)
				throw new ArgumentNullException("member");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

            return member.DeclaringType;
		}
        public override MemberAccessModifiers GetMemberAccessModifiers(MemberInfo member)
		{
			if (member == null)
				throw new ArgumentNullException("member");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (member is Type)
				return (member as Type).IsPublic ? MemberAccessModifiers.Public : MemberAccessModifiers.Private;
			else if (member is PropertyInfo)
				return ((member as PropertyInfo).GetGetMethod() != null ? MemberAccessModifiers.PublicGet : 0) | ((member as PropertyInfo).GetSetMethod() != null ? MemberAccessModifiers.PublicSet : 0);
			else if (member is FieldInfo)
				return (member as FieldInfo).IsPublic ? MemberAccessModifiers.Public : MemberAccessModifiers.Private;

			return MemberAccessModifiers.Private;
		}
	}
}
